diff options
| author | Factiven <[email protected]> | 2023-12-24 13:03:54 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-12-24 13:03:54 +0700 |
| commit | 50a0f0240d7fef133eb5acc1bea2b1168b08e9db (patch) | |
| tree | 307e09e505580415a58d64b5fc3580e9235869f1 /pages/api/v2/episode/[id].tsx | |
| parent | Update README.md (#104) (diff) | |
| download | moopa-50a0f0240d7fef133eb5acc1bea2b1168b08e9db.tar.xz moopa-50a0f0240d7fef133eb5acc1bea2b1168b08e9db.zip | |
migrate to typescript
Diffstat (limited to 'pages/api/v2/episode/[id].tsx')
| -rw-r--r-- | pages/api/v2/episode/[id].tsx | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/pages/api/v2/episode/[id].tsx b/pages/api/v2/episode/[id].tsx new file mode 100644 index 0000000..b646126 --- /dev/null +++ b/pages/api/v2/episode/[id].tsx @@ -0,0 +1,333 @@ +// @ts-nocheck + +import axios from "axios"; +import { rateLimiterRedis, rateSuperStrict, redis } from "@/lib/redis"; +import appendMetaToEpisodes from "@/utils/appendMetaToEpisodes"; +import { NextApiRequest, NextApiResponse } from "next"; +import { AnifyEpisode, ConsumetInfo, EpisodeData } from "types"; +import { Episode } from "@/types/api/Episode"; +import { getProviderWithMostEpisodesAndImage } from "@/utils/parseMetaData"; + +let CONSUMET_URI: string | null; + +CONSUMET_URI = process.env.API_URI || null; +if (CONSUMET_URI && CONSUMET_URI.endsWith("/")) { + CONSUMET_URI = CONSUMET_URI.slice(0, -1); +} + +const isAscending = (data: Episode[]) => { + for (let i = 1; i < data.length; i++) { + if (data[i].number < data[i - 1].number) { + return false; + } + } + return true; +}; + +export interface RawEpisodeData { + map?: boolean; + providerId: string; + episodes: { + sub: Episode[]; + dub: Episode[]; + }; +} + +function filterData(data: RawEpisodeData[], type: "sub" | "dub") { + // Filter the data based on the type (sub or dub) and providerId + const filteredData = data.map((item) => { + if (item?.map === true) { + if (item.episodes[type].length === 0) { + return null; + } else { + return { + ...item, + episodes: Object?.entries(item.episodes[type]).map( + ([id, episode]) => ({ + ...episode, + }) + ), + }; + } + } + return item; + }); + + const noEmpty = filteredData.filter((i) => i !== null); + return noEmpty; +} + +async function fetchConsumet(id?: string | string[] | undefined) { + try { + const fetchData = async (dub?: any) => { + const { data } = await axios.get<ConsumetInfo>( + `${CONSUMET_URI}/meta/anilist/info/${id}${dub ? "?dub=true" : ""}` + ); + if (data?.message === "Anime not found" && data?.length < 1) { + return []; + } + + if (dub) { + if (!data?.episodes?.some((i) => i.id.includes("dub"))) return []; + } + + const reformatted = data.episodes?.map((item) => ({ + id: item.id, + title: item?.title || null, + img: item?.image || null, + number: item?.number || null, + createdAt: item?.airDate || null, + description: item?.description || null, + })); + + return reformatted; + }; + + const [subData, dubData] = await Promise.all([ + fetchData(), + fetchData(true), + ]); + + if (subData.every((i) => i.id?.includes("dub"))) { + // replace dub in title with sub + subData.forEach((item) => { + if (item.id?.includes("dub")) { + item.id = item.id?.replace("dub", "anime"); + } + }); + console.log("replaced dub with sub"); + } + + const array = [ + { + map: true, + providerId: "gogoanime", + episodes: { + sub: isAscending(subData) ? subData : subData.reverse(), + dub: isAscending(dubData) ? dubData : dubData.reverse(), + }, + }, + ]; + + return array; + } catch (error: any) { + console.error("Error fetching and processing data:", error.message); + return []; + } +} + +async function fetchAnify(id?: string) { + try { + const { data } = await axios.get<AnifyEpisode[]>( + `https://api.anify.tv/episodes/${id}` + ); + + if (!data) { + return []; + } + + const filtered = data.filter( + (item) => item.providerId !== "9anime" && item.providerId !== "kass" + ); + + return filtered; + } catch (error: any) { + console.error("Error fetching and processing data:", error.message); + return []; + } +} + +async function fetchCoverImage(id: string, available = false) { + try { + if (!process.env.API_KEY) { + return []; + } + + if (available) { + return null; + } + + const { data } = await axios.get( + `https://api.anify.tv/content-metadata/${id}` + ); + + if (!data) { + return []; + } + + const getData = getProviderWithMostEpisodesAndImage(data); + // const getData = data?.[0]?.data; + + return getData.data; + } catch (error: any) { + console.error("Error fetching and processing data:", error.message); + return []; + } +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { id, releasing = "false", dub = false, refresh = null } = req.query; + + // if releasing is true then cache for 1 hour, if it false cache for 1 month; + let cacheTime = null; + if (releasing === "true") { + cacheTime = 60 * 60; // 1 hour + } else if (releasing === "false") { + cacheTime = 60 * 60 * 24 * 30; // 1 month + } + + let cached; + let meta; + let headers: any = {}; + + if (redis) { + try { + const ipAddress: any = req.socket.remoteAddress; + refresh + ? await rateSuperStrict.consume(ipAddress) + : await rateLimiterRedis.consume(ipAddress); + + headers = refresh + ? await rateSuperStrict.get(ipAddress) + : await rateLimiterRedis.get(ipAddress); + } catch (error: any) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${getTimeFromMs( + error.msBeforeNext + )}`, + remaining: error.remainingPoints, + }); + } + + meta = await redis.get(`meta:${id}`); + const parsedMeta = JSON.parse(meta); + if (parsedMeta?.length === 0) { + await redis.del(`meta:${id}`); + console.log("deleted meta cache"); + meta = null; + } + + if (refresh) { + await redis.del(`episode:${id}`); + } else { + cached = await redis.get(`episode:${id}`); + if (cached?.length === 0) { + await redis.del(`episode:${id}`); + cached = null; + } + } + } + + if (cached && !refresh) { + if (dub) { + const filteredData: EpisodeData[] = filterData(JSON.parse(cached), "dub"); + + let filtered = filteredData.filter((item) => + item?.episodes?.some((epi) => epi.hasDub !== false) + ); + + if (meta) { + filtered = await appendMetaToEpisodes(filtered, JSON.parse(meta)); + } + + res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); + res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); + + return res + .status(200) + .json(filtered?.filter((i) => i?.providerId !== "9anime")); + } else { + const filteredData = filterData(JSON.parse(cached), "sub"); + + let filtered = filteredData; + + if (meta) { + filtered = await appendMetaToEpisodes(filteredData, JSON.parse(meta)); + } + + res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); + res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); + + return res + .status(200) + .send(filtered?.filter((i) => i?.providerId !== "9anime")); + } + } else { + const [consumet, anify, cover] = await Promise.all([ + fetchConsumet(id), + fetchAnify(id), + fetchCoverImage(id, meta), + ]); + + // const hasImage = consumet.map((i) => + // i.episodes?.sub?.some( + // (e) => e.img !== null || !e.img.includes("https://s4.anilist.co/") + // ) + // ); + + let subDub = "sub"; + if (dub) { + subDub = "dub"; + } + + const rawData = [...consumet, ...anify]; + + const filteredData = filterData(rawData, subDub); + + let data = filteredData; + + if (meta) { + data = await appendMetaToEpisodes(filteredData, JSON.parse(meta)); + } else if ( + cover && + // !cover?.some((item: { img: null }) => item.img === null) && + cover?.length > 0 + ) { + if (redis) await redis.set(`meta:${id}`, JSON.stringify(cover)); + data = await appendMetaToEpisodes(filteredData, cover); + } + + if (redis && cacheTime !== null && rawData?.length > 0) { + await redis.set( + `episode:${id}`, + JSON.stringify(rawData), + "EX", + cacheTime + ); + } + + if (dub) { + const filtered = data.filter( + (item) => !item.episodes.some((epi) => epi.hasDub === false) + ); + return res + .status(200) + .json(filtered.filter((i) => i.episodes.length > 0)); + } + + if (redis) { + res.setHeader("X-RateLimit-Limit", refresh ? 1 : 50); + res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); + res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); + } + + return res.status(200).json(data.filter((i) => i.episodes.length > 0)); + } +} + +function getTimeFromMs(time: number) { + const timeInSeconds = time / 1000; + + if (timeInSeconds >= 3600) { + const hours = Math.floor(timeInSeconds / 3600); + return `${hours} hour${hours > 1 ? "s" : ""}`; + } else if (timeInSeconds >= 60) { + const minutes = Math.floor(timeInSeconds / 60); + return `${minutes} minute${minutes > 1 ? "s" : ""}`; + } else { + return `${timeInSeconds} second${timeInSeconds > 1 ? "s" : ""}`; + } +} |